"The shortest distance between two points is a straight line." - Archimedes
要做出移動平均線,在 Charts 裡面最直觀拿來達成這個任務用的物件,就是 LineCartView。但這個物件只能吃 LineChartData 的資料。我們要做的是在圖上,同時呈現 K 線和均線,同時,也不排除新的隕石級需求砸下來的狀況。所以,我們要使用的類別就是 CombinedChartView。CombinedChartView 裡面的 Data 可以塞 candleData, lineData 未來有 barData 也是塞的進去。
import Foundation
/// MA 線的點
struct MovingAveragePoint {
let x: Double
let y: Double
}
這個物件專門處理 SockKLine array → MovingAveragePoint Array
注意,裡面有個處理五個收盤價總合的地方。如果讀者會 dynamic programming 的手法,當總合往前算的時候,減去最後一根 k 棒的收盤價,再加上前一根 k 棒的收盤價。理論上來說,效能會比較好。
但我現在在趕稿和趕專案的狀態,所以這一段的優化就留給讀者進行了。
import Foundation
/// 專門處理移動平均線的物件
struct MovingAverageUtility {
/// 將已得到的 K 棒,轉出 5 MA 的資料
/// - Parameter stockTicks: 傳入的 K 棒需先保證 date 從遠排到近
/// - Returns: 回傳的 MA 點也保證會是 x 從小排到大
func get5MAPoints(from stockTicks: [StockKLine]) -> [MovingAveragePoint] {
let maPeriod = 5 // 5MA
let tickIndices = Array(stockTicks.indices).sorted { $0 > $1 } // 先拿出 index 並把 index 從大到小排
var maPoints = [MovingAveragePoint]()
for tickIndex in tickIndices {
let startIndex = tickIndex - 5 + 1
if !stockTicks.indices.contains(startIndex) || !stockTicks.indices.contains(tickIndex) {
break
}
let needCalculateTicks = Array(stockTicks[startIndex...tickIndex])
// 從這裡開始計算 n 日內收盤價的平均,有更有效率的做法,像是動態規畫的方式實作。這一段就留給讀者自行優化
let closePriceList = needCalculateTicks.map { tick in
return tick.close ?? 0 // 這邊先不考慮如果沒有收盤價(暫停交易)的情況,如果有,應該把這個點去除掉,使用 filter 即可
}
let sum = closePriceList.reduce(0, +) //總合
let maValue = sum / Double(maPeriod)
let point = MovingAveragePoint(x: Double(tickIndex), y: maValue)
maPoints.append(point)
}
return maPoints.sorted { $0.x < $1.x }
}
}
當做出 MA Point 物件,和專門處理 MA 計算的工具,剩下的就是在 CandleStickData 算完後,把 K 線資料丟進 MA 計算工具,就會得到 LineChartData 了。
台股申購日曆
IT鐵人賽Demo App
下方是這次 D1 ~ D12 的完成品,可以下載來試
App Store - 台股申購日曆